package elephant.application;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import elephant.Container;
import elephant.Lifecycle;
import elephant.Server;
import elephant.service.Service;
import elephant.util.ObjectPrinter;

/**
 * 
 * @author skydu
 *
 */
public abstract class Application implements Lifecycle{
	//
	private static Logger logger=LoggerFactory.getLogger(Application.class);
	//
	private Map<Class<?>,AutowiredObject>autowiredMap=
			new ConcurrentHashMap<Class<?>, AutowiredObject>();
	//
	/**
	 * 
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T getWired(Class<T>clazz){
		AutowiredObject ao=autowiredMap.get(clazz);
		if(ao==null){
			return null;
		}
		T instance =(T) ao.instance;
		return instance;
	} 
	/**
	 * 
	 * @param clazz
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public <T> T createWired(Class<T>clazz)throws Exception{
		T instance =clazz.newInstance();
		AutowiredObject AutowiredObject=new AutowiredObject();
		AutowiredObject.clazz=clazz;
		AutowiredObject.instance=instance;
		autowiredMap.put(clazz,AutowiredObject);
		if(logger.isDebugEnabled()){
			logger.debug("create wired object {}",clazz.getName());
		}
		//
		Container container=Container.get();
		Set<Field> fields=getField(clazz);
		for(Field f:fields){
			if(f.isAnnotationPresent(Autowired.class)){
				Autowired aw=f.getAnnotation(Autowired.class);
				AutowiredField af=new AutowiredField(f.getName(),f.getType(),aw.shared());
				AutowiredObject.fields.add(af);
				f.setAccessible(true);
				Class<?>fieldType=f.getType();
				if(Service.class.isAssignableFrom(fieldType)){
					Object target=container.getService(
							(Class<? extends Service>) fieldType);
					if(target!=null){
						f.set(instance,target);		
					}else{
						logger.warn("can not find Autowired service:"+fieldType);
					}
				}else if(Server.class.isAssignableFrom(fieldType)){
					Object target=container.getServer(
							(Class<? extends Server>) fieldType);
					if(target!=null){
						f.set(instance,target);		
					}else{
						logger.warn("can not find Autowired server:"+fieldType);
					}
				}else{
					if(aw.shared()){
						AutowiredObject ao=autowiredMap.get(fieldType);
						Object target=(ao==null?null:ao.instance);
						if(target==null){
							target=createWired(fieldType);
						}
						f.set(instance,target);	
	
					}else{
						Object target=createWired(fieldType);
						f.set(instance,target);	
					}
				}
			}
		}
		//
		return instance;
	}
	//
	private Set<Field> getField(Class<?>clazz){
		HashSet<Field>result=new HashSet<Field>();
		result.addAll(Arrays.asList(clazz.getDeclaredFields()));
		if(clazz.getSuperclass()!=null){
			getParentField(clazz,result);
		}
		return result;
	}
	//
	private void getParentField(Class<?>clazz,Set<Field>fields){
		Class<?>superClass=clazz.getSuperclass();
		if(clazz.getSuperclass()==null){
			return;
		}
		fields.addAll(Arrays.asList(superClass.getDeclaredFields()));
		getParentField(superClass, fields);
	}
	//
	public List<AutowiredObject>getAutowiredObjects(){
		return new ArrayList<>(autowiredMap.values());
	}
	//
	@Override
	public String dump() {
		ObjectPrinter op=new ObjectPrinter();
		op.section(getClass().getSimpleName());
		op.print("version",getClass().getPackage().getImplementationVersion());
		op.print("class",getClass().getName());
		return op.toString();
	}
}
